#ifndef MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H
#define MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H

#include "muduo/base/Logging.h"
#include "muduo/net/Buffer.h"
#include "muduo/net/Endian.h"
#include "muduo/net/TcpConnection.h"

  // AI: “中间层”在这里指的是把消息编解码的逻辑从核心业务代码中抽离出来，作为一个独立的组件（LengthHeaderCodec）放在网络框架和业务逻辑之间起桥梁作用
  // 解耦职责
  // - 网络层（TcpServer）只管收发原始字节流，不需要知道这些字节如何组成完整消息。
  // - 业务层（ChatServer）只关心“完整字符串消息”，不需要处理粘包、拆包等细节。
  // - 中间层（LengthHeaderCodec）负责把字节流转换为完整消息，并在发送时加上长度前缀
class LengthHeaderCodec : muduo::noncopyable
{
 public:
  typedef std::function<void (const muduo::net::TcpConnectionPtr&,
                                const muduo::string& message,
                                muduo::Timestamp)> StringMessageCallback;

  explicit LengthHeaderCodec(const StringMessageCallback& cb)
    : messageCallback_(cb)
  {
  }

  void onMessage(const muduo::net::TcpConnectionPtr& conn,
                 muduo::net::Buffer* buf,
                 muduo::Timestamp receiveTime) // 分包的代码: 解析数据往往比生成数据更复杂，分包、打包也不例外。
  {
    while (buf->readableBytes() >= kHeaderLen) // kHeaderLen == 4 CH07: 用了while循环来反复读取数据，直到Buffer中的数据不够一条完整的消息。请读者思考，如果换成if(buf->readableBytes() >= kHeaderLen)会有什么后果。
    {
      // FIXME: use Buffer::peekInt32()
      const void* data = buf->peek();
      int32_t be32 = *static_cast<const int32_t*>(data); // SIGBUS CH07: 有潜在的问题，在某些不支持非对齐内存访问的体系结构上会造成SIGBUS core dump，读取消息长度应该改用Buffer::peekInt32()
      const int32_t len = muduo::net::sockets::networkToHost32(be32);
      if (len > 65536 || len < 0)
      {
        LOG_ERROR << "Invalid length " << len;
        conn->shutdown();  // FIXME: disable reading
        break;
      }
      else if (buf->readableBytes() >= len + kHeaderLen) // 在写decoder的时候一定要在收到完整的消息, 之后再retrieve整条消息​，除非接收方使用复杂的状态机来解码
      {
        buf->retrieve(kHeaderLen);
        muduo::string message(buf->peek(), len); // 构造完整的消息
        messageCallback_(conn, message, receiveTime); // 通过messageCallback_回调用户代码
        buf->retrieve(len);
      }
      else
      {
        break;
      }
    }
  }

  // FIXME: TcpConnectionPtr
  void send(muduo::net::TcpConnection* conn,
            const muduo::StringPiece& message)
  {
    muduo::net::Buffer buf;
    buf.append(message.data(), message.size());
    int32_t len = static_cast<int32_t>(message.size());
    int32_t be32 = muduo::net::sockets::hostToNetwork32(len);
    buf.prepend(&be32, sizeof be32); // muduo Buffer有一个很好的功能，它在头部预留了8个字节的空间，这样prepend()操作就不需要移动已有的数据，效率较高。
    conn->send(&buf);
  }

 private:
  StringMessageCallback messageCallback_;
  const static size_t kHeaderLen = sizeof(int32_t);
};

#endif  // MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H
